﻿using System;
using System.Linq;
using System.Reflection;
using Borg.Properties;

namespace Borg
{
    public class Qualifier : RuntimeElement
    {
        private static readonly string ExtensionParamTypeName = typeof(Extension).FullName;
        private static readonly string ExtensionContextParamTypeName = typeof(ExtensionContext).FullName;
        private static readonly string ExtensionEventArgsTypeName = typeof(ExtensionEventArgs).FullName;
        private static readonly string SlotEventArgsTypeName = typeof(SlotEventArgs).FullName;
        private static readonly string CompositionEventArgsTypeName = typeof(CompositionEventArgs).FullName;
        private static readonly string TagEventArgsTypeName = typeof(TagEventArgs).FullName;
        
        internal const BindingFlags ConstructorBindingFlags =
            BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

        internal const BindingFlags EventHandlerBindingFlags =
            BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
        
        private const QualificationState ContractErrorState = QualificationState.ContractNoSlotDefinitions;
        private const QualificationState SlotDefinitionErrorState = QualificationState.TagDefinitionTypeNotFound;
        private const QualificationState ParamDefinitionErrorState = QualificationState.TagDefinitionTypeNotFound;
        private const QualificationState TagDefinitionErrorState = QualificationState.ParamPlugNotQualified;
        private const QualificationState PluginErrorState = QualificationState.PluginNoExtensions;
        private const QualificationState ExtensionTypeErrorState = QualificationState.ParamPlugNotQualified;
        private const QualificationState PlugTypeErrorState = QualificationState.ParamPlugNotQualified;
        private const QualificationState ParamErrorState = QualificationState.ParamPlugNotQualified | QualificationState.ParamNameAlreadyExists;
        private const QualificationState SlotTypeErrorState = QualificationState.PlugOnPluggedMethodNotFound;

        static Qualifier()
        { }

        protected internal Qualifier(Runtime runtime)
            : base(runtime)
        { }

        public bool Qualify(Contract contract)
        {
            if (contract == null)
                throw new ArgumentNullException("contract");

            lock (Runtime)
                return InternalQualify(contract);
        }

        public bool Qualify(SlotDefinition slotDefinition)
        {
            if (slotDefinition == null)
                throw new ArgumentNullException("slotDefinition");

            lock (Runtime)
                return InternalQualify(slotDefinition);
        }

        public bool Qualify(ParamDefinition paramDefinition)
        {
            if (paramDefinition == null)
                throw new ArgumentNullException("paramDefinition");

            lock (Runtime)
                return InternalQualify(paramDefinition);
        }

        public bool Qualify(TagDefinition tagDefinition)
        {
            if (tagDefinition == null)
                throw new ArgumentNullException("tagDefinition");

            lock (Runtime)
                return InternalQualify(tagDefinition);
        }

        public bool Qualify(Plugin plugin)
        {
            if (plugin == null)
                throw new ArgumentNullException("plugin");

            lock (Runtime)
                return InternalQualify(plugin);
        }

        public bool Qualify(ExtensionType extensionType)
        {
            if (extensionType == null)
                throw new ArgumentNullException("extensionType");

            lock (Runtime)
                return InternalQualify(extensionType);
        }

        public bool Qualify(PlugType plugType)
        {
            if (plugType == null)
                throw new ArgumentNullException("plugType");

            lock (Runtime)
                return InternalQualify(plugType);
        }

        public bool Qualify(Param param)
        {
            if (param == null)
                throw new ArgumentNullException("param");

            lock (Runtime)
                return InternalQualify(param);
        }

        public bool Qualify(SlotType slotType)
        {
            if (slotType == null)
                throw new ArgumentNullException("slotType");

            lock (Runtime)
                return InternalQualify(slotType);
        }

        public bool IsQualified(Contract contract)
        {
            if (contract == null)
                throw new ArgumentNullException("contract");

            lock (Runtime)
                return InternalIsQualified(contract);
        }

        public bool IsQualified(SlotDefinition slotDefinition)
        {
            if (slotDefinition == null)
                throw new ArgumentNullException("slotDefinition");

            lock (Runtime)
                return InternalIsQualified(slotDefinition);
        }

        public bool IsQualified(ParamDefinition paramDefinition)
        {
            if (paramDefinition == null)
                throw new ArgumentNullException("paramDefinition");

            lock (Runtime)
                return InternalIsQualified(paramDefinition);
        }

        public bool IsQualified(TagDefinition tagDefinition)
        {
            if (tagDefinition == null)
                throw new ArgumentNullException("tagDefinition");

            lock (Runtime)
                return InternalIsQualified(tagDefinition);
        }

        public bool IsQualified(Plugin plugin)
        {
            if (plugin == null)
                throw new ArgumentNullException("plugin");

            lock (Runtime)
                return InternalIsQualified(plugin);
        }

        public bool IsQualified(ExtensionType extensionType)
        {
            if (extensionType == null)
                throw new ArgumentNullException("extensionType");

            lock (Runtime)
                return InternalIsQualified(extensionType);
        }

        public bool IsQualified(PlugType plugType)
        {
            if (plugType == null)
                throw new ArgumentNullException("plugType");

            lock (Runtime)
                return InternalIsQualified(plugType);
        }

        public bool IsQualified(Param param)
        {
            if (param == null)
                throw new ArgumentNullException("param");

            lock (Runtime)
                return InternalIsQualified(param);
        }

        public bool IsQualified(SlotType slotType)
        {
            if (slotType == null)
                throw new ArgumentNullException("slotType");

            lock (Runtime)
                return InternalIsQualified(slotType);
        }

        public bool IsQualificationMissing(Qualifiable qualifiable)
        {
            if (qualifiable == null)
                throw new ArgumentNullException("qualifiable");

            lock (Runtime)
                return InternalIsQualificationMissing(qualifiable);
        }

        internal bool InternalIsQualified(Contract contract)
        {
            return (contract.QualificationState &
                QualificationState.ContractNoSlotDefinitions) == QualificationState.None;
        }

        internal bool InternalIsQualified(SlotDefinition slotDefinition)
        {
            return (slotDefinition.QualificationState &
                QualificationState.TagDefinitionTypeNotFound) == QualificationState.None;
        }

        internal bool InternalIsQualified(ParamDefinition paramDefinition)
        {
            return (paramDefinition.QualificationState &
                QualificationState.TagDefinitionTypeNotFound) == QualificationState.None;
        }

        internal bool InternalIsQualified(TagDefinition tagDefinition)
        {
            return (tagDefinition.QualificationState &
                QualificationState.ParamPlugNotQualified) == QualificationState.None;
        }

        internal bool InternalIsQualified(Plugin plugin)
        {
            return (plugin.QualificationState &
                QualificationState.PluginNoExtensions) == QualificationState.None;
        }

        internal bool InternalIsQualified(ExtensionType extensionType)
        {
            return (extensionType.QualificationState &
                QualificationState.ParamPlugNotQualified) == QualificationState.None;
        }

        internal bool InternalIsQualified(PlugType plugType)
        {
            return (plugType.QualificationState &
                QualificationState.ParamPlugNotQualified) == QualificationState.None;
        }

        internal bool InternalIsQualified(Param param)
        {
            return (param.QualificationState &
                (QualificationState.ParamPlugNotQualified |
                QualificationState.ParamNameAlreadyExists)) == QualificationState.None;
        }

        internal bool InternalIsQualified(SlotType slotType)
        {
            return (slotType.QualificationState &
                QualificationState.PlugOnPluggedMethodNotFound) == QualificationState.None;
        }

        internal bool InternalIsQualificationMissing(Qualifiable qualifiable)
        {
            return (qualifiable.QualificationState &
                QualificationState.QualificationMissing) == QualificationState.QualificationMissing;
        }

        internal bool InternalWeakQualify(Contract contract)
        {
            return
                InternalIsQualified(contract) ||
                InternalIsQualificationMissing(contract) &&
                InternalQualify(contract);
        }

        internal bool InternalWeakQualify(SlotDefinition slotDefinition)
        {
            return
                InternalIsQualified(slotDefinition) ||
                InternalIsQualificationMissing(slotDefinition) &&
                InternalQualify(slotDefinition);
        }

        internal bool InternalWeakQualify(ParamDefinition paramDefinition)
        {
            return
                InternalIsQualified(paramDefinition) ||
                InternalIsQualificationMissing(paramDefinition) &&
                InternalQualify(paramDefinition);
        }

        internal bool InternalWeakQualify(TagDefinition tagDefinition)
        {
            return
                InternalIsQualified(tagDefinition) ||
                InternalIsQualificationMissing(tagDefinition) &&
                InternalQualify(tagDefinition);
        }

        internal bool InternalWeakQualify(Plugin plugin)
        {
            return
                InternalIsQualified(plugin) ||
                InternalIsQualificationMissing(plugin) &&
                InternalQualify(plugin);
        }

        internal bool InternalWeakQualify(ExtensionType extensionType)
        {
            return
                InternalIsQualified(extensionType) ||
                InternalIsQualificationMissing(extensionType) &&
                InternalQualify(extensionType);
        }

        internal bool InternalWeakQualify(PlugType plugType)
        {
            return
                InternalIsQualified(plugType) ||
                InternalIsQualificationMissing(plugType) &&
                InternalQualify(plugType);
        }

        internal bool InternalWeakQualify(Param param)
        {
            return
                InternalIsQualified(param) ||
                InternalIsQualificationMissing(param) &&
                InternalQualify(param);
        }

        internal bool InternalWeakQualify(SlotType slotType)
        {
            return
                InternalIsQualified(slotType) ||
                InternalIsQualificationMissing(slotType) &&
                InternalQualify(slotType);
        }

        internal bool InternalQualify(Contract contract)
        {
            contract.QualificationState = QualificationState.None;
            OnQualifying("Contract", contract);

            var assembly =
                Loader.InternalIsLoaded(contract) || Loader.InternalReflectionOnlyIsLoaded(contract)
                    ? Loader.InternalGetAssembly(contract)
                    : Loader.InternalReflectionOnlyLoad(contract);
            
            if (CheckContractAssemblyLoaded(contract, assembly))
            {
                CheckContractHasSlotDefinitions(contract);
                CheckUniqueContractName(contract);
            }

            if (!InternalIsQualified(contract))
                return false;
            
            OnQualified("Contract", contract);
            
            return true;
        }

        private bool CheckContractAssemblyLoaded(Contract contract, Assembly assembly)
        {
            if (!(assembly == null))
                return true;

            OnError(
                "Contract",
                contract,
                string.Format(
                    Messages.Qualifier_AssemblyCouldNotBeLoaded,
                    contract.AssemblyUri));
            
            var cntrct = contract;
            var state = (long)(cntrct.QualificationState | QualificationState.ContractAssemblyCouldNotBeLoaded);
            cntrct.QualificationState = (QualificationState)state;
            
            return false;
        }

        private void CheckContractHasSlotDefinitions(Contract contract)
        {
            if (contract.SlotDefinitions.Count != 0)
                return;
            
            OnError("Contract", contract, Messages.Qualifier_ContractHasNoSlotDefinitions);
            
            var cntrct = contract;
            var state = (long)(cntrct.QualificationState | QualificationState.ContractNoSlotDefinitions);
            cntrct.QualificationState = (QualificationState)state;
        }

        private void CheckUniqueContractName(Contract contract)
        {
            if (!TypeStore.InternalGetContracts(contract.Name).
                Any(other => other != contract && InternalIsQualified(other)))
                return;

            OnWarning("Contract", contract, string.Format(
                Messages.Qualifier_ElementAlreadyExists, "Contract", contract.Name));
            
            var cntrct = contract;
            var state = (long)(cntrct.QualificationState | QualificationState.ContractNameAlreadyExists);
            cntrct.QualificationState = (QualificationState)state;
        }

        internal bool InternalQualify(SlotDefinition slotDefinition)
        {
            slotDefinition.QualificationState = QualificationState.None;
            
            var contract = slotDefinition.Contract;
            var contractQualified = InternalWeakQualify(contract);
            
            OnQualifying("Slot definition", slotDefinition);
            
            if (!CheckContractQualified(slotDefinition, contractQualified))
                return false;
            
            var assembly = Loader.InternalGetAssembly(contract);
            var type = Loader.InternalGetType(assembly, slotDefinition.TypeName);
            
            if (CheckSlotDefinitionInterfaceDefined(slotDefinition, assembly, type))
                CheckUniqueSlotDefinitionName(slotDefinition);
            
            if (!InternalIsQualified(slotDefinition))
                return false;
            
            OnQualified("Slot definition", slotDefinition);
            
            return true;
        }

        private bool CheckContractQualified(SlotDefinition slotDefinition, bool contractQualified)
        {
            if (contractQualified)
                return true;

            OnError("Slot definition", slotDefinition,
                string.Format(Messages.Qualifier_ElementIsNotQualified, "Contract"));

            var slotDef = slotDefinition;
            var state = (long)(slotDef.QualificationState | QualificationState.SlotDefinitionContractNotQualified);
            slotDef.QualificationState = (QualificationState)state;
            
            return false;
        }

        private bool CheckSlotDefinitionInterfaceDefined(SlotDefinition slotDefinition, Assembly assembly, Type type)
        {
            if (!(type == null))
                return true;
            
            OnError("Slot definition", slotDefinition, string.Format(
                Messages.Qualifier_InterfaceNotInContractAssembly, slotDefinition.TypeName, assembly.FullName));
            
            var slotDef = slotDefinition;
            var state = (long)(slotDef.QualificationState | QualificationState.SlotDefinitionTypeNotInContractAssembly);
            slotDef.QualificationState = (QualificationState)state;
            
            return false;
        }

        private void CheckUniqueSlotDefinitionName(SlotDefinition slotDefinition)
        {
            if (!TypeStore.InternalGetSlotDefinitions(slotDefinition.Name).
                Any(other => other != slotDefinition && InternalIsQualified(other)))
                return;
            
            OnError("Slot definition", slotDefinition, string.Format(
                Messages.Qualifier_ElementAlreadyExists, "Slot definition", slotDefinition.Name));
            
            var slotDef = slotDefinition;
            var state = (long)(slotDef.QualificationState | QualificationState.SlotDefinitionAlreadyExists);
            slotDef.QualificationState = (QualificationState)state;
        }

        internal bool InternalQualify(ParamDefinition paramDefinition)
        {
            paramDefinition.QualificationState = QualificationState.None;

            var slotDefinitionQualified = InternalWeakQualify(paramDefinition.SlotDefinition);
            OnQualifying("Param definition", paramDefinition);

            if (!CheckSlotDefinitionQualified(paramDefinition, slotDefinitionQualified))
                return false;

            var type = Loader.InternalGetType(paramDefinition);

            if (CheckParamDefinitionTypeFound(paramDefinition, type))
                CheckDefaultValueAssinable(paramDefinition, type);

            if (!InternalIsQualified(paramDefinition))
                return false;
            
            OnQualified("Param definition", paramDefinition);

            return true;
        }

        private bool CheckSlotDefinitionQualified(ParamDefinition paramDefinition, bool slotDefinitionQualified)
        {
            if (slotDefinitionQualified)
                return true;
            
            OnError("Param definition", paramDefinition, string.Format(
                Messages.Qualifier_ElementIsNotQualified, "Slot definition"));

            var paramDef = paramDefinition;
            var state = (long)(paramDef.QualificationState | QualificationState.ParamDefinitionSlotDefinitionNotQualified);
            paramDef.QualificationState = (QualificationState)state;
            
            return false;
        }

        private bool CheckParamDefinitionTypeFound(ParamDefinition paramDefinition, Type type)
        {
            if (!(type == null))
                return true;

            OnError("Param definition", paramDefinition, string.Format(
                Messages.Qualifier_ParamDefinitionTypeNotFound, paramDefinition.TypeName));

            var paramDef = paramDefinition;
            var state = (long)(paramDef.QualificationState | QualificationState.ParamDefinitionTypeNotFound);
            paramDef.QualificationState = (QualificationState)state;
            
            return false;
        }

        private void CheckDefaultValueAssinable(ParamDefinition paramDefinition, Type type)
        {
            if (paramDefinition.DefaultParam == null)
                return;

            var paramVal = paramDefinition.DefaultParam.Value;

            if (paramVal == null)
            {
                if (!type.IsValueType)
                    return;
            }
            else if (type.IsInstanceOfType(paramVal))
                return;

            OnError("Param definition", paramDefinition, string.Format(
                Messages.Qualifier_ParamDefalutValueNotAssignable, type.FullName));
            
            var paramDef = paramDefinition;
            var state = (long)(paramDef.QualificationState | QualificationState.ParamDefinitionDefaultValueNotAssignable);
            paramDef.QualificationState = (QualificationState)state;
        }

        internal bool InternalQualify(TagDefinition tagDefinition)
        {
            tagDefinition.QualificationState = QualificationState.None;

            var slotDefinitionQualified = InternalWeakQualify(tagDefinition.SlotDefinition);
            OnQualifying("Tag definition", tagDefinition);
            
            if (!CheckSlotDefinitionQualified(tagDefinition, slotDefinitionQualified) ||
                !InternalIsQualified(tagDefinition))
                return false;
            
            OnQualified("Tag definition", tagDefinition);
            
            return true;
        }

        private bool CheckSlotDefinitionQualified(TagDefinition tagDefinition, bool slotDefinitionQualified)
        {
            if (slotDefinitionQualified)
                return true;
            
            OnError("Tag definition", tagDefinition, string.Format(
                Messages.Qualifier_ElementIsNotQualified, "Tag definition"));
            
            var tagDef = tagDefinition;
            var state = (long)(tagDef.QualificationState | QualificationState.TagDefinitionSlotDefinitionNotQualified);
            tagDef.QualificationState = (QualificationState)state;
            
            return false;
        }

        internal bool InternalQualify(Plugin plugin)
        {
            plugin.QualificationState = QualificationState.None;
            OnQualifying("Plugin", plugin);

            var assembly = Loader.InternalIsLoaded(plugin) ||
                           Loader.InternalReflectionOnlyIsLoaded(plugin)
                               ? Loader.InternalGetAssembly(plugin)
                               : Loader.InternalReflectionOnlyLoad(plugin);
            
            if (CheckPluginAssemblyLoaded(plugin, assembly))
            {
                CheckPluginHasExtensions(plugin);
                CheckUniquePluginName(plugin);
            }

            if (!InternalIsQualified(plugin))
                return false;
            
            OnQualified("Plugin", plugin);
            
            return true;
        }

        private bool CheckPluginAssemblyLoaded(Plugin plugin, Assembly assembly)
        {
            if (!(assembly == null))
                return true;
            
            OnError("Plugin", plugin, string.Format(
                Messages.Qualifier_AssemblyCouldNotBeLoaded, plugin.AssemblyUri));
            
            var plugn = plugin;
            var state = (long)(plugn.QualificationState | QualificationState.PluginAssemblyCouldNotBeLoaded);
            plugn.QualificationState = (QualificationState)state;
            
            return false;
        }

        private void CheckPluginHasExtensions(Plugin plugin)
        {
            if (plugin.ExtensionTypes.Count != 0)
                return;
            
            OnError("Plugin", plugin, Messages.Qualifier_PluginHasNoExtensions);

            var plugn = plugin;
            var state = (long)(plugn.QualificationState | QualificationState.PluginNoExtensions);
            plugn.QualificationState = (QualificationState)state;
        }

        private void CheckUniquePluginName(Plugin plugin)
        {
            if (!TypeStore.InternalGetPlugins(plugin.Name).
                Any(other => other != plugin && InternalIsQualified(other)))
                return;
            
            OnWarning("Plugin", plugin, string.Format(
                Messages.Qualifier_ElementAlreadyExists, "Plugin", plugin.Name));
            
            var plugn = plugin;
            var state = (long)(plugn.QualificationState | QualificationState.PluginNameAlreadyExists);
            plugn.QualificationState = (QualificationState)state;
        }

        internal bool InternalQualify(ExtensionType extensionType)
        {
            extensionType.QualificationState = QualificationState.None;

            var plugin = extensionType.Plugin;
            var pluginQualified = InternalWeakQualify(plugin);

            OnQualifying("Extension type", extensionType);
            
            if (!CheckPluginQualified(extensionType, pluginQualified))
                return false;
            
            var assembly = Loader.InternalGetAssembly(plugin);
            var type = Loader.InternalGetType(assembly, extensionType.TypeName);
            
            if (CheckExtensionTypeDefined(extensionType, assembly, type))
            {
                CheckExtensionConstructor(extensionType, type);
                CheckExtensionHasPlugs(extensionType);
                QualifyExtensionEventHandlers(extensionType, type);
            }
            
            if (!InternalIsQualified(extensionType))
                return false;
            
            OnQualified("Extension type", extensionType);
            
            return true;
        }

        private bool CheckPluginQualified(ExtensionType extensionType, bool pluginQualified)
        {
            if (pluginQualified)
                return true;
            
            OnError("Extension type", extensionType, string.Format(
                Messages.Qualifier_ElementIsNotQualified, "Plugin"));
            
            var extType = extensionType;
            var state = (long)(extType.QualificationState | QualificationState.ExtensionPluginNotQualified);
            extType.QualificationState = (QualificationState)state;
            
            return false;
        }

        private bool CheckExtensionTypeDefined(ExtensionType extensionType, Assembly assembly, Type type)
        {
            if (!(type == null))
                return true;
            
            OnError("Extension type", extensionType, string.Format(
                Messages.Qualifier_ClassNotInPluginAssembly, extensionType.TypeName, assembly.FullName));
            
            var extType = extensionType;
            var state = (long)(extType.QualificationState | QualificationState.ExtensionTypeNotInPluginAssembly);
            extType.QualificationState = (QualificationState)state;
            
            return false;
        }

        private void CheckExtensionConstructor(ExtensionType extensionType, Type type)
        {
            if (SetConstructorParameter(extensionType, type))
                return;
            
            OnError("Extension type", extensionType, Messages.Qualifier_InvalidConstructor);
            
            var extType = extensionType;
            var state = (long)(extType.QualificationState | QualificationState.ExtensionInvalidConstructor);
            extType.QualificationState = (QualificationState)state;
        }

        private void CheckExtensionHasPlugs(ExtensionType extensionType)
        {
            if (extensionType.PlugTypes.Count != 0 || extensionType == Runtime.RootExtensionType)
                return;
            
            OnError("Extension type", extensionType, Messages.Qualifier_ExtensionHasNoPlugs);
            
            var extType = extensionType;
            var state = (long)(extType.QualificationState | QualificationState.ExtensionNoPlugs);
            extType.QualificationState = (QualificationState)state;
        }

        internal bool InternalQualify(PlugType plugType)
        {
            plugType.QualificationState = QualificationState.None;

            var extensionType = plugType.ExtensionType;
            var extensionTypeQualified = InternalWeakQualify(extensionType);
            
            if (extensionTypeQualified)
            {
                var environmentUri = plugType.AssemblyElement.EnvironmentUri;

                foreach (var slotDefinition in TypeStore.InternalGetSlotDefinitions(plugType.Name).
                    Where(slotDefinition => !(environmentUri != slotDefinition.AssemblyElement.EnvironmentUri)))
                {
                    plugType.SlotDefinition = slotDefinition;
                    break;
                }
            }

            OnQualifying("Plug type", plugType);

            if (!CheckExtensionTypeQualified(plugType, extensionTypeQualified))
                return false;

            var type = Loader.InternalGetType(
                Loader.InternalGetAssembly(extensionType.Plugin), extensionType.TypeName);
            
            if (CheckQualifiedSlotDefinitionFound(plugType))
            {
                CheckSlotInterfaceImplemented(plugType, type);
                CheckParamsDefined(plugType);
            }

            QualifyPlugEventHandlers(plugType, type);
            
            if (!InternalIsQualified(plugType))
                return false;

            OnQualified("Plug type", plugType);

            return true;
        }

        private bool CheckExtensionTypeQualified(PlugType plugType, bool extensionTypeQualified)
        {
            if (extensionTypeQualified)
                return true;
            
            OnError("Plug type", plugType, string.Format(
                Messages.Qualifier_ElementIsNotQualified, "Extension type"));
            
            var plug = plugType;
            var state = (long)(plug.QualificationState | QualificationState.PlugExtensionNotQualified);
            plug.QualificationState = (QualificationState)state;
            
            return false;
        }

        private bool CheckQualifiedSlotDefinitionFound(PlugType plugType)
        {
            if (plugType.SlotDefinition != null)
                return true;
            
            OnError("Plug type", plugType, string.Format(
                Messages.Qualifier_SlotDefinitionNotFound, plugType.Name));
            
            var plug = plugType;
            var state = (long)(plug.QualificationState | QualificationState.PlugSlotDefinitionNotFound);
            plug.QualificationState = (QualificationState)state;
            
            return false;
        }

        private void CheckSlotInterfaceImplemented(PlugType plugType, Type type)
        {
            var slotDefinition = plugType.SlotDefinition;
            var assembly = Loader.GetAssembly(slotDefinition.Contract);
            var asmtype = assembly.GetType(slotDefinition.TypeName);
            var interfaces = type.GetInterfaces();
            
            if (type.Assembly.ReflectionOnly == assembly.ReflectionOnly)
            {
                if (interfaces.Any(intrface => !(intrface != asmtype)))
                    return;
            }
            else
            {
                var fullName = asmtype.FullName;

                foreach (var intrface in interfaces.Where(intrface => intrface.FullName == fullName))
                {
                    if (!assembly.ReflectionOnly)
                        return;

                    Loader.InternalSetAssembly(slotDefinition.Contract, intrface.Assembly);
                    
                    return;
                }
            }

            OnError("Plug type", plugType, string.Format(
                Messages.Qualifier_InterfaceNotImplemented, asmtype.FullName));
            
            var plug = plugType;
            var state = (long)(plug.QualificationState | QualificationState.PlugInterfaceNotImplemented);
            plug.QualificationState = (QualificationState)state;
        }

        private void CheckParamsDefined(PlugType plugType)
        {
            foreach (var paramDef in plugType.SlotDefinition.ParamDefinitions)
            {
                if (plugType.Params.Contains(paramDef.Name))
                {
                    if (!InternalWeakQualify(plugType.Params[paramDef.Name]))
                    {
                        OnError("Plug type", plugType, string.Format(
                            Messages.Qualifier_ParamNotQualified, paramDef.Name));
                        
                        var plug = plugType;
                        var state = (long)(plug.QualificationState | QualificationState.PlugParamNotQualified);
                        plug.QualificationState = (QualificationState)state;
                    }
                }
                else if (InternalWeakQualify(paramDef) && paramDef.DefaultParam == null)
                {
                    OnError("Plug type", plugType, string.Format(
                        Messages.Qualifier_ParameterMissing, paramDef.Name));
                    
                    var plug = plugType;
                    var state = (long)(plug.QualificationState | QualificationState.PlugParamMissing);
                    plug.QualificationState = (QualificationState)state;
                }
            }
        }

        public bool InternalQualify(Param param)
        {
            param.QualificationState = QualificationState.None;

            var plugTypeQualified = InternalWeakQualify(param.PlugType);
            OnQualifying("Param", param);
            
            if (!CheckPlugTypeQualified(param, plugTypeQualified))
                return false;
            
            CheckParamAssignable(param);

            if (!InternalIsQualified(param))
                return false;

            OnQualified("Param", param);
            
            return true;
        }

        private bool CheckPlugTypeQualified(Param param, bool plugTypeQualified)
        {
            if (plugTypeQualified)
                return true;
            
            OnError("Param", param, string.Format(
                Messages.Qualifier_ElementIsNotQualified, "Plug type"));
            
            var parm = param;
            var state = (long)(parm.QualificationState | QualificationState.ParamPlugNotQualified);
            parm.QualificationState = (QualificationState)state;
            
            return false;
        }

        private void CheckParamAssignable(Param param)
        {
            var slotDef = param.PlugType.SlotDefinition;
            
            if (!slotDef.ParamDefinitions.Contains(param.Name))
                return;
            
            var paramDef = slotDef.ParamDefinitions[param.Name];
            
            if (!InternalWeakQualify(paramDef))
                return;
            
            var type = Loader.InternalGetType(paramDef);
            
            if (type.IsInstanceOfType(param.Value))
                return;
            
            OnError("Param", param, string.Format(
                Messages.Qualifier_ParamValueNotAssignable, type.FullName));
            
            var parm = param;
            var state = (long)(parm.QualificationState | QualificationState.ParamNotAssignable);
            parm.QualificationState = (QualificationState)state;
        }

        public bool InternalQualify(SlotType slotType)
        {
            slotType.QualificationState = QualificationState.None;

            var extensionType = slotType.ExtensionType;
            var extensionTypeQualified = InternalWeakQualify(extensionType);
            
            if (extensionTypeQualified)
                slotType.SlotDefinition = 
                    TypeStore.InternalGetSlotDefinitions(slotType.Name).FirstOrDefault(InternalWeakQualify);
            
            OnQualifying("Slot type", slotType);
            
            if (!CheckExtensionTypeQualified(slotType, extensionTypeQualified))
                return false;

            var type = Loader.InternalGetType(
                Loader.InternalGetAssembly(extensionType.Plugin), extensionType.TypeName);
            
            CheckQualifiedSlotDefinitionFound(slotType);
            QualifySlotEventHandlers(slotType, type);

            if (!InternalIsQualified(slotType))
                return false;
            
            OnQualified("Slot type", slotType);

            return true;
        }

        private bool CheckExtensionTypeQualified(SlotType slotType, bool extensionTypeQualified)
        {
            if (extensionTypeQualified)
                return true;

            OnError("Slot type", slotType, string.Format(
                Messages.Qualifier_ElementIsNotQualified, "Extension type"));

            var slot = slotType;
            var state = (long)(slot.QualificationState | QualificationState.SlotExtensionNotQualified);
            slot.QualificationState = (QualificationState)state;
            
            return false;
        }

        private void CheckQualifiedSlotDefinitionFound(SlotType slotType)
        {
            if (slotType.SlotDefinition != null)
                return;

            OnError("Slot type", slotType, string.Format(
                Messages.Qualifier_SlotDefinitionNotFound, slotType.Name));
            
            var slot = slotType;
            var state = (long)(slot.QualificationState | QualificationState.SlotSlotDefinitionNotFound);
            slot.QualificationState = (QualificationState)state;
        }

        private void QualifyExtensionEventHandlers(ExtensionType extensionType, Type type)
        {
            if (extensionType.OnActivated != null && !CheckEventHandler(type, extensionType.OnActivated, ExtensionEventArgsTypeName))
            {
                OnWarning("Extension type", extensionType, string.Format(
                    Messages.Qualifier_EventHandlerMethodNotFound, (object)"OnActivated",
                    (object)type.FullName, (object)extensionType.OnActivated, (object)"ExtensionEventArgs"));
                
                var extension = extensionType;
                var state = (long)(extension.QualificationState | QualificationState.ExtensionOnCreatedMethodNotFound);
                extension.QualificationState = (QualificationState)state;
            }

            if (extensionType.OnDeactivated == null ||
                CheckEventHandler(type, extensionType.OnDeactivated, ExtensionEventArgsTypeName))
                return;
            
            OnWarning("Extension type", extensionType, string.Format(
                Messages.Qualifier_EventHandlerMethodNotFound, (object)"OnDeactivated", (object)type.FullName,
                (object)extensionType.OnDeactivated, (object)"ExtensionEventArgs"));
            
            var ext = extensionType;
            var stat = (long)(ext.QualificationState | QualificationState.ExtensionOnDestroyedMethodNotFound);
            ext.QualificationState = (QualificationState)stat;
        }

        private void QualifyPlugEventHandlers(PlugType plugType, Type type)
        {
            if (plugType.OnPlugged != null &&
                !CheckEventHandler(type, plugType.OnPlugged, CompositionEventArgsTypeName))
            {
                OnWarning("Plug type", plugType, string.Format(
                    Messages.Qualifier_EventHandlerMethodNotFound, (object)"OnPlugged",
                    (object)type.FullName, (object)plugType.OnPlugged, (object)"CompositionEventArgs"));
                
                var plug = plugType;
                var state = (long)(plug.QualificationState | QualificationState.PlugOnPluggedMethodNotFound);
                plug.QualificationState = (QualificationState)state;
            }

            if (plugType.OnTagged != null &&
                !CheckEventHandler(type, plugType.OnTagged, TagEventArgsTypeName))
            {
                OnWarning("Plug type", plugType, string.Format(
                    Messages.Qualifier_EventHandlerMethodNotFound, (object)"OnTagged",
                    (object)type.FullName, (object)plugType.OnTagged, (object)"TagEventArgs"));

                var plug = plugType;
                var state = (long)(plug.QualificationState | QualificationState.PlugOnTaggedMethodNotFound);
                plug.QualificationState = (QualificationState)state;
            }

            if (plugType.OnUnplugged != null &&
                !CheckEventHandler(type, plugType.OnUnplugged, CompositionEventArgsTypeName))
            {
                OnWarning("Plug type", plugType, string.Format(
                    Messages.Qualifier_EventHandlerMethodNotFound, (object)"OnUnplugged",
                    (object)type.FullName, (object)plugType.OnUnplugged, (object)"CompositionEventArgs"));
                
                var plug = plugType;
                var state = (long)(plug.QualificationState | QualificationState.PlugOnUnpluggedMethodNotFound);
                plug.QualificationState = (QualificationState)state;
            }

            if (plugType.OnUntagged == null ||
                CheckEventHandler(type, plugType.OnUntagged, TagEventArgsTypeName))
                return;

            OnWarning("Plug type", plugType, string.Format(
                Messages.Qualifier_EventHandlerMethodNotFound, (object)"OnUntagged",
                (object)type.FullName, (object)plugType.OnUntagged, (object)"TagEventArgs"));

            var plugTyp = plugType;
            var stat = (long)(plugTyp.QualificationState | QualificationState.PlugOnUntaggedMethodNotFound);
            plugTyp.QualificationState = (QualificationState)stat;
        }

        private void QualifySlotEventHandlers(SlotType slotType, Type type)
        {
            if (slotType.OnOpened != null &&
                !CheckEventHandler(type, slotType.OnOpened, SlotEventArgsTypeName))
            {
                OnWarning("Slot type", slotType, string.Format(
                    Messages.Qualifier_EventHandlerMethodNotFound, (object)"OnOpened",
                    (object)type.FullName, (object)slotType.OnOpened, (object)"SlotEventArgs"));

                var slot = slotType;
                var state = (long)(slot.QualificationState | QualificationState.SlotOnOpenedMethodNotFound);
                slot.QualificationState = (QualificationState)state;
            }

            if (slotType.OnPlugged != null &&
                !CheckEventHandler(type, slotType.OnPlugged, CompositionEventArgsTypeName))
            {
                OnWarning("Slot type", slotType, string.Format(
                    Messages.Qualifier_EventHandlerMethodNotFound, (object)"OnPlugged",
                    (object)type.FullName, (object)slotType.OnPlugged, (object)"CompositionEventArgs"));

                var slot = slotType;
                var state = (long)(slot.QualificationState | QualificationState.SlotOnPluggedMethodNotFound);
                slot.QualificationState = (QualificationState)state;
            }

            if (slotType.OnTagged != null &&
                !CheckEventHandler(type, slotType.OnTagged, TagEventArgsTypeName))
            {
                OnWarning("Slot type", slotType, string.Format(
                    Messages.Qualifier_EventHandlerMethodNotFound, (object)"OnTagged",
                    (object)type.FullName, (object)slotType.OnTagged, (object)"TagEventArgs"));

                var slot = slotType;
                var state = (long)(slot.QualificationState | QualificationState.SlotOnTaggedMethodNotFound);
                slot.QualificationState = (QualificationState)state;
            }

            if (slotType.OnClosed != null &&
                !CheckEventHandler(type, slotType.OnClosed, SlotEventArgsTypeName))
            {
                OnWarning("Slot type", slotType, string.Format(
                    Messages.Qualifier_EventHandlerMethodNotFound, (object)"OnClosed",
                    (object)type.FullName, (object)slotType.OnClosed, (object)"SlotEventArgs"));

                var slot = slotType;
                var state = (long)(slot.QualificationState | QualificationState.SlotOnClosedMethodNotFound);
                slot.QualificationState = (QualificationState)state;
            }

            if (slotType.OnUnplugged != null &&
                !CheckEventHandler(type, slotType.OnUnplugged, CompositionEventArgsTypeName))
            {
                OnWarning("Slot type", slotType, string.Format(
                    Messages.Qualifier_EventHandlerMethodNotFound, (object)"OnUnplugged",
                    (object)type.FullName, (object)slotType.OnUnplugged, (object)"CompositionEventArgs"));

                var slot = slotType;
                var state = (long)(slot.QualificationState | QualificationState.SlotOnUnpluggedMethodNotFound);
                slot.QualificationState = (QualificationState)state;
            }

            if (slotType.OnUntagged == null ||
                CheckEventHandler(type, slotType.OnUntagged, TagEventArgsTypeName))
                return;

            OnWarning("Slot type", slotType, string.Format(
                Messages.Qualifier_EventHandlerMethodNotFound, (object)"OnUntagged",
                (object)type.FullName, (object)slotType.OnUntagged, (object)"TagEventArgs"));

            var slotTyp = slotType;
            var stat = (long)(slotTyp.QualificationState | QualificationState.SlotOnUntaggedMethodNotFound);
            slotTyp.QualificationState = (QualificationState)stat;
        }

        private void OnQualifying(string elementKind, MetaElement element)
        { }

        private void OnWarning(string elementKind, MetaElement element, string message)
        {
            Logger.Log(
                VerbosityLevel.Normal,
                LogType.Warning,
                this,
                Messages.Qualifier_WarningOnQualifying,
                (object) elementKind,
                (object) element.Id,
                (object) element.Name,
                (object) message);
        }

        private void OnError(string elementKind, MetaElement element, string message)
        {
            Logger.Log(
                VerbosityLevel.Minimal,
                LogType.Error,
                this,
                Messages.Qualifier_ErrorOnQualifying,
                (object) elementKind,
                (object) element.Id,
                (object) element.Name,
                (object) message);
        }

        private void OnQualified(string elementKind, MetaElement element)
        { }

        private static bool SetConstructorParameter(ExtensionType extensionType, Type type)
        {
            var constructors = type.GetConstructors(
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

            var setState = false;
            
            foreach (var parameters in
                from MethodBase methodBase in constructors
                select methodBase.GetParameters())
            {
                if (MatchParameters(parameters, ExtensionContextParamTypeName))
                {
                    extensionType.ConstructorType = ConstructorType.ExtensionContext;
                    return true;
                }
                
                if (MatchParameters(parameters, ExtensionParamTypeName))
                {
                    extensionType.ConstructorType = ConstructorType.Extension;
                    setState = true;
                }

                if (parameters.Length != 0 || setState)
                    continue;

                extensionType.ConstructorType = ConstructorType.None;
                setState = true;
            }

            return setState;
        }

        internal static bool CheckEventHandler(Type type, string eventHandlerName, string parameterTypeName)
        {
            return type.GetMethods(
                BindingFlags.Instance |
                BindingFlags.Static |
                BindingFlags.Public |
                BindingFlags.NonPublic).Cast<MethodBase>().
                Any(method => MatchMethod(method, eventHandlerName, parameterTypeName));
        }

        private static bool MatchMethod(MethodBase method, string methodName, string parameterTypeName)
        {
            return
                method.Name == methodName &&
                MatchParameters(method.GetParameters(), parameterTypeName);
        }

        private static bool MatchParameters(ParameterInfo[] parameters, string parameterTypeName)
        {
            return
                parameters.Length == 1 &&
                parameters[0].ParameterType.FullName == parameterTypeName;
        }
    }
}
